package kvdb

import (
	"errors"
	"sync"

	"hundsun.com/hsl/hschain/common/log"

	"github.com/golang/protobuf/proto"
	"hundsun.com/hsl/hschain/common/config"
	"hundsun.com/hsl/hschain/common/crypto"
	"hundsun.com/hsl/hschain/common/types"
	pbcom "hundsun.com/hsl/hschain/protos/common"
	dbm "hundsun.com/hsl/hschain/store/db"
	"hundsun.com/hsl/hschain/store/state"
)

const (
	// StateName ...
	StateName = "kvdb"
)

func init() {
	state.Register(StateName, New)
}

// Store kv store backend
type Store struct {
	db    dbm.Database
	cache map[string]map[string]*pbcom.KeyValue
	mut   *sync.Mutex
}

// New Store module
func New(cfg *config.PluginConfig, db dbm.Database) (state.State, error) {
	st := &Store{db, make(map[string]map[string]*pbcom.KeyValue), &sync.Mutex{}}
	return st, nil
}

// Get s with statehash from Store
func (s *Store) Get(datas *pbcom.StateGet) ([][]byte, error) {
	// kvdb为单版本状态数据库因此不需要对状态hash以及高度做验证
	return s.db.Gets(datas.Keys)
}

// GetStateInfo 获取最新状态数据信息
func (s *Store) GetStateInfo() (*pbcom.StateInfo, error) {
	infoDB := state.NewInfoDB(s.db)
	return infoDB.GetStateInfo()
}

// SetMemory set s to the memory of Store
func (s *Store) SetMemory(datas *pbcom.StateSet, sync bool) ([]byte, error) {
	if len(datas.KVs) == 0 {
		log.Errorf("store kv SetMemory,use preStateHash as stateHash for set is null")
		return datas.PreStateHash, nil
	}
	mp := make(map[string]*pbcom.KeyValue)
	for _, kv := range datas.KVs {
		mp[string(kv.Key)] = kv
	}
	// 当前高度新计算的状态hash
	hash := calcHash(datas)
	// append LatestStateInfoKV
	kv, err := state.InfoKV(&pbcom.StateInfo{
		Ty:        StateName,
		Number:    datas.Number,
		StateHash: hash})
	if err != nil {
		log.Errorf("store kv SetMemory LatestStateInfoKV error %v", err)
		return hash, err
	}
	mp[string(kv.Key)] = kv
	s.mut.Lock()
	s.cache[string(hash)] = mp
	s.mut.Unlock()
	return hash, nil
}

// Commit s in the memory of Store
func (s *Store) Commit(hash []byte) error {
	kvmap, err := s.get2DelCache(hash)
	if err != nil {
		return err
	}
	s.save(kvmap)
	return nil
}

// RollBack rollback data and sign
func (s *Store) RollBack(datas *pbcom.StateSet) error {
	err := errors.New("unsupport rollback")
	log.Errorf("Store error %v", err)
	panic(err.Error())
	return err
}

func (s *Store) get2DelCache(hash []byte) (map[string]*pbcom.KeyValue, error) {
	s.mut.Lock()
	defer s.mut.Unlock()
	kvmap, ok := s.cache[string(hash)]
	if !ok {
		log.Errorf("store kvdb commit error %v", types.ErrNotFound)
		return nil, types.ErrNotFound
	}
	if len(kvmap) == 0 {
		log.Errorf("store kvdb commit did nothing for set is nil")
		delete(s.cache, string(hash))
		return nil, nil
	}
	delete(s.cache, string(hash))
	return kvmap, nil
}

func (s *Store) save(mp map[string]*pbcom.KeyValue) {
	if len(mp) == 0 {
		return
	}
	batch := s.db.NewBatch(true)
	for _, kv := range mp {
		if kv.Value == nil {
			batch.Delete(kv.Key)
		} else {
			batch.Set(kv.Key, kv.Value)
		}
	}
	batch.Write()
	batch.Reset()
}

func calcHash(datas proto.Message) []byte {
	b, _ := proto.Marshal(datas)
	hash, _ := crypto.Hash(b)
	return hash
}
